package hexgame;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Set;

public class GameLogic {
	
	/**
	 * Checks to see if a player has won, and if so returns the player which has won
	 * @param state - the gamestate to check
	 * @return The HexPlayer which has won, or HexPlayer.NONE if there is no winner yet
	 */
	public static HexPlayer playerWon(GameState state) {
		
		// Go down the length of the board
		for(int i=0; i<state.boardSize; i++) {
			// If the hex is the red player on the left edge
			if(state.getHexState(0, i) == HexPlayer.RED_LOCAL) {
				// See if it's connected to the far column
				if(isConnectedToEdge(new HexMove(0, i, HexPlayer.RED_LOCAL), state, new ArrayList<HexMove>(), state.boardSize - 1, -1)) { 
					// If so, red player has won
					return HexPlayer.RED_LOCAL;
				}
			}
			
			// If the hex is the blue player on the top edge
			if(state.getHexState(i, 0) == HexPlayer.BLUE_REMOTE) {
				// See if it's connected to the bottom row
				if(isConnectedToEdge(new HexMove(i, 0, HexPlayer.BLUE_REMOTE), state, new ArrayList<HexMove>(), -1, state.boardSize - 1)) { 
					return HexPlayer.BLUE_REMOTE;
				}
			}
		}
		
		return HexPlayer.NONE;
	}

	/**
	 * Checks if the move passed is valid for the specified GameState
	 * @param state - the state to check on
	 * @param move - the move to check
	 * @return true or false
	 */
	public static boolean isValidMove(GameState state, HexMove move) {
		
		if(state.localPlayerTurn && move.getPlayer() != HexPlayer.RED_LOCAL) {
			return false;
		}
	
		if(!state.localPlayerTurn && move.getPlayer() != HexPlayer.BLUE_REMOTE) {
			return false;
		}
		
		// If the move is on the board
		if(GameLogic.isOnBoard(move, state)) {
			// and that hex has not already got a move on it
			if(state.getHexState(move) == HexPlayer.NONE) {
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Performs the specified move on the passed GameState.
	 * Should be a valid move, will throw an error otherwise
	 * @param previous - the gamestate to perform the move on
	 * @param gameMove - the move to perform
	 * @return - a gamestate which has had the move applied to
	 */
	public static void makeMove (GameState previous, HexMove gameMove) {
		// Check whether the move is valid so we can give a move useful error message (rather than arrayoutofbounds)
		if(!isValidMove(previous, gameMove)) {
			throw new Error("Invalid move applied to gamestate");
		}
		
		// Set the HexPlayer for that hex to the one for the move passed in
		previous.board[gameMove.getX()][gameMove.getY()] = gameMove.getPlayer();
		
		previous.localPlayerTurn = !previous.localPlayerTurn;
	}
	
	/**
	 * Recusively checks whether the start hexagon is connected directly to the specified edge
	 */
	private static boolean isConnectedToEdge(HexMove start, GameState state, List<HexMove> checked, int targetRow, int targetColumn) {
		checked.add(start);
		
		Collection<HexMove> list = getConnectingHexagons(start, state);
		
		for(HexMove move : list) {
			if(move.getX() == targetRow || move.getY() == targetColumn) {
				return true;
			}
			
			if(!checked.contains(move)) {
				if(isConnectedToEdge(move, state, checked, targetRow, targetColumn)) {
					return true;
				}
			}
		}
		return false;
	}
	
	
	/**
	 * Checks whether the move is placed on the board of the passed GameState
	 * @param position - The position of the move. The player is ignored
	 * @param state - the GameState containing the board to check
	 */
	public static boolean isOnBoard(HexMove position, GameState state) {
		
		int upperLimit = (state.boardSize - 1);
		if(position.x >= 0 && position.x <= upperLimit) {
			if(position.y >= 0 && position.y <= upperLimit) {
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Gets hexagons surrounding (but excluding) position, which are of either type position.getPlayer() or HexPlayer.NONE
	 * @param position - The position (and HexPlayer type) to check
	 * @param state - The GameState to get the neighbours from
	 * @return a Set of the positions
	 */
	public static Collection<HexMove> getConnectingMoves(HexMove position, GameState state) {
		Set<HexMove> moveSet = new HashSet<HexMove>();
		moveSet.addAll(getConnectingHexagons(new HexMove(position.getX(), position.getY(), HexPlayer.NONE), state));
		moveSet.addAll(getConnectingHexagons(new HexMove(position.getX(), position.getY(), position.getPlayer()), state));

		return moveSet;
	}
	
	/**
	 * Gets hexagons surrounding (but excluding) position, which are of either type position.getPlayer() or HexPlayer.NONE
	 * 
	 * Not a very efficient way to do this, but it works and we're short on time.
	 * @param position - The position (and HexPlayer type) to check
	 * @param state - The GameState to get the neighbours from
	 * @return a Set of the positions
	 */
	public static Collection<HexMove> getZeroConnectingMoves(HexMove position, GameState state) {
		Set<HexMove> moveSet = new HashSet<HexMove>();
		moveSet.addAll(getValidZeroConnectedHexagons(new HexMove(position.getX(), position.getY(), HexPlayer.NONE), state));
		moveSet.addAll(getValidZeroConnectedHexagons(new HexMove(position.getX(), position.getY(), position.getPlayer()), state));

		return moveSet;
	}
	
	/**
	 * Gets hexagons surrounding (but excluding) position, which are of type HexPlayer.NONE
	 * @param position - The position to check
	 * @param state - The GameState to get the neighbours from
	 * @return a Set of the positions
	 */
	public static Collection<HexMove> getValidConnectingMoves(HexMove position, GameState state) {
		Set<HexMove> moveSet = new HashSet<HexMove>();
		moveSet.addAll(getConnectingHexagons(new HexMove(position.getX(), position.getY(), HexPlayer.NONE), state));
		
		return moveSet;
	}
	/**
	 * Returns a list of all the Hexagons which are next to position and of the same State (RED/BLUE/NONE)
	 * Checking for NONE isn't very useful though
	 * 
	 * Excludes the position passed in.
	 */
	public static Collection<HexMove> getConnectingHexagons(HexMove position, GameState state) {
		
		int x = position.getX();
		int y = position.getY();
		
		// the list will store at most 6 values
		List<HexMove> list = new ArrayList<HexMove>(6);
		
		// Add all possibilities
		list.add(new HexMove(x, y-1, position.getPlayer()));
		list.add(new HexMove(x, y+1, position.getPlayer()));
		
		list.add(new HexMove(x-1, y, position.getPlayer()));
		list.add(new HexMove(x+1, y, position.getPlayer()));
		
		list.add(new HexMove(x+1, y-1, position.getPlayer()));
		list.add(new HexMove(x-1, y+1, position.getPlayer()));
		
		// Check whether they are on the board and are of the same HexPlayer state
		ListIterator<HexMove> it = list.listIterator();
		while(it.hasNext()) {
			HexMove move = it.next();
			if(!isOnBoard(move, state)) {
				it.remove();
				continue;
			}
			if(state.getHexState(move) != position.getPlayer()) {
				it.remove();
				continue;
			}
		}
		
		return list;
	}
	
	/**
	 * See https://en.wikipedia.org/wiki/Hex_(board_game)#Paths for 0-connected concept
	 * Gets a list of 
	 * @param position - The Start position
	 * @param state - The GameState to find the zero connected hexagons on
	 * @return A list of moves which would result in a zero connected path
	 */
	public static Collection<HexMove> getValidZeroConnectedHexagons(HexMove position, GameState state) {
		int x = position.getX();
		int y = position.getY();
		
		// the list will store at most 12 values
		ArrayList<HexMove> bridgeList = new ArrayList<HexMove>(GameLogic.getValidConnectingMoves(position, state));
		
		// Add all bridge possibilities
		// Bridges are only zero connected if the two squares between them are clear or the same colour as the player, 
		// but then they aren't really a bridge.. anyway
		
		// -1,+1
		HexMove depend1 = new HexMove(x-1, y+2);
		// +0,+1
		HexMove depend2 = new HexMove(x, y+1);
		// -1,+0
		HexMove depend3 = new HexMove(x-1, y);
		// +0,-1
		HexMove depend4 = new HexMove(x, y-1);
		// +1, 0
		HexMove depend5 = new HexMove(x+1, y);
		// +1,-1
		HexMove depend6 = new HexMove(x+1, y-1);
		
		//relies on -1,+1 and +0,+1
		addIfDependenciesFree(bridgeList, new HexMove(x-1, y+2, position.getPlayer()), state, depend1, depend2);
		
		//relies on -1,+0 and +0,-1
		addIfDependenciesFree(bridgeList, new HexMove(x-1, y-1, position.getPlayer()), state, depend3, depend4);
		
		// relies on -1,+0 and -1,+1
		addIfDependenciesFree(bridgeList, new HexMove(x-2, y+1, position.getPlayer()), state, depend3, depend1);
		// relies on +0,+1 and +1,0
		addIfDependenciesFree(bridgeList, new HexMove(x+1, y+1, position.getPlayer()), state, depend2, depend5);
		
		// relies on +0,-1 and +1,-1
		addIfDependenciesFree(bridgeList, new HexMove(x+1, y-2, position.getPlayer()), state, depend3, depend6);
		// relies on +1,-1 and +1,+0
		addIfDependenciesFree(bridgeList, new HexMove(x+2, y-1, position.getPlayer()), state, depend6, depend5);
		
		return bridgeList;
	}
	
	/**
	 * Used by getValidZeroConnectedHexagons to only add the bridge positions in if they would be zero connected
	 */
	private static void addIfDependenciesFree(Collection<HexMove> list, HexMove move, GameState state, HexMove depend1, HexMove depend2) {
		if(GameLogic.isOnBoard(move, state) && GameLogic.isOnBoard(depend1, state) && GameLogic.isOnBoard(depend2, state)) {
			if(state.getHexState(move) == HexPlayer.NONE) {
				if(state.getHexState(depend1) == move.getPlayer() || state.getHexState(depend1) == HexPlayer.NONE) {
					if(state.getHexState(depend2) == move.getPlayer() || state.getHexState(depend2) == HexPlayer.NONE) {
						list.add(move);
					}
				}
			}
		}
	}
	
	/*
	 * This function was added later... It should replace some code in the above functions
	 * 
	 */
	private static HexMove[] getLinkPositions(int x, int y, int dx, int dy) {
		HexMove depend1 = null, depend2 = null;
		if(dx == -1) {
			if(dy == 2) {
				depend1 = new HexMove(x-1, y+2);
				// +0,+1
				depend2 = new HexMove(x, y+1);
			}
			else if(dy == -1) {
				// -1,+0
				depend1 = new HexMove(x-1, y);
				// +0,-1
				depend2 = new HexMove(x, y-1);
			}
		}
		else if(dx == -2 && dy == 1) {
			// -1,+1
			depend1 = new HexMove(x-1, y+2);
			// -1,+0
			depend2 = new HexMove(x-1, y);
		}
		else if(dx == 1) {
			if(dy == 1) {
				// +0,+1
				depend1 = new HexMove(x, y+1);
				// +1, 0
				depend2 = new HexMove(x+1, y);
			}
			else if(dy == -1) {
				// -1,+0
				depend1 = new HexMove(x-1, y);
				// +1,-1
				depend2 = new HexMove(x+1, y-1);
			}
		}
		else if(dx == 1 && dy == -1) {
			// +1, 0
			depend1 = new HexMove(x+1, y);
			// +1,-1
			depend2 = new HexMove(x+1, y-1);
		}
		
		if(depend1 == null) {
			return null;
		}
		else return new HexMove[] {depend1, depend2};
	}
 
	public static HexMove[] getLinkPositions(HexMove move, HexMove hexMove) {
		int dx, dy;
		
		dx = move.getX() - hexMove.getX();
		dy = move.getY() - hexMove.getY();
		return getLinkPositions(move.getX(), move.getY(), dx, dy);
	}
}
